#include <fc/crypto/bigint.hpp>
#include <fc/utility.hpp>
#include <openssl/bn.h>
#include <fc/variant.hpp>
#include <fc/crypto/base64.hpp>

#include <fc/exception/exception.hpp>
#include "../byteswap.hpp"

namespace fc {
bigint::bigint(const char* bige, uint32_t l) {
    n = BN_bin2bn((const unsigned char*)bige, l, NULL);
    FC_ASSERT(n != nullptr);
}
bigint::bigint(const std::vector<char>& bige) {
    n = BN_bin2bn((const unsigned char*)bige.data(), bige.size(), NULL);
    FC_ASSERT(n != nullptr);
}
bigint::bigint(BIGNUM* in) {
    n = BN_dup(in);
}
bigint::bigint()
    : n(BN_new()) {}

BIGNUM*
bigint::dup() const {
    return BN_dup(n);
}

bigint::bigint(uint64_t value) {
    uint64_t big_endian_value = bswap_64(value);
    n                         = BN_bin2bn((const unsigned char*)&big_endian_value, sizeof(big_endian_value), NULL);
}

bigint::bigint(const bigint& c) {
    n = BN_dup(c.n);
}

bigint::bigint(bigint&& b) {
    n   = b.n;
    b.n = 0;
}

bigint::~bigint() {
    if(n != 0)
        BN_free(n);
}

bool
bigint::is_negative() const {
    return BN_is_negative(n);
}

int64_t
bigint::to_int64() const {
    FC_ASSERT(BN_num_bits(n) <= 63);
    size_t   size      = BN_num_bytes(n);
    uint64_t abs_value = 0;
    BN_bn2bin(n, (unsigned char*)&abs_value + (sizeof(uint64_t) - size));
    return BN_is_negative(n) ? -(int64_t)bswap_64(abs_value) : bswap_64(abs_value);
}

int64_t
bigint::log2() const {
    return BN_num_bits(n);
}
bool
bigint::operator<(const bigint& c) const {
    return BN_cmp(n, c.n) < 0;
}
bool
bigint::operator>(const bigint& c) const {
    return BN_cmp(n, c.n) > 0;
}
bool
bigint::operator>=(const bigint& c) const {
    return BN_cmp(n, c.n) >= 0;
}
bool
bigint::operator==(const bigint& c) const {
    return BN_cmp(n, c.n) == 0;
}
bool
bigint::operator!=(const bigint& c) const {
    return BN_cmp(n, c.n) != 0;
}
bigint::operator bool() const {
    return !BN_is_zero(n);
}
bigint
bigint::operator++(int) {
    bigint tmp = *this;
    *this      = *this + bigint(1);
    return tmp;
}
bigint&
bigint::operator++() {
    return *this = *this + bigint(1);
}
bigint
bigint::operator--(int) {
    bigint tmp = *this;
    *this      = *this - bigint(1);
    return tmp;
}
bigint&
bigint::operator--() {
    return *this = *this - bigint(1);
}

bigint
bigint::operator+(const bigint& a) const {
    bigint tmp(*this);
    BN_add(tmp.n, n, a.n);
    return tmp;
}
bigint&
bigint::operator+=(const bigint& a) {
    bigint tmp(*this);
    BN_add(tmp.n, n, a.n);
    std::swap(*this, tmp);
    return *this;
}
bigint&
bigint::operator-=(const bigint& a) {
    bigint tmp(*this);
    BN_sub(tmp.n, n, a.n);
    std::swap(*this, tmp);
    return *this;
}

bigint bigint::operator*(const bigint& a) const {
    BN_CTX* ctx = BN_CTX_new();
    bigint  tmp(*this);
    BN_mul(tmp.n, n, a.n, ctx);
    BN_CTX_free(ctx);
    return tmp;
}
bigint
bigint::operator/(const bigint& a) const {
    BN_CTX* ctx = BN_CTX_new();
    bigint  tmp;  //(*this);
    BN_div(tmp.n, NULL, n, a.n, ctx);
    BN_CTX_free(ctx);
    return tmp;
}
bigint
bigint::operator%(const bigint& a) const {
    BN_CTX* ctx = BN_CTX_new();
    bigint  tmp;  //(*this);
    BN_mod(tmp.n, n, a.n, ctx);
    BN_CTX_free(ctx);
    return tmp;
}

bigint
bigint::operator/=(const bigint& a) {
    BN_CTX* ctx = BN_CTX_new();
    bigint  tmp;  //*this);
    BN_div(tmp.n, NULL, n, a.n, ctx);
    fc_swap(tmp.n, n);
    BN_CTX_free(ctx);
    return tmp;
}
bigint
bigint::operator*=(const bigint& a) {
    auto tmp = *this * a;
    *this    = std::move(tmp);
    return *this;
}
bigint&
bigint::operator>>=(uint32_t i) {
    bigint tmp;
    BN_rshift(tmp.n, n, i);
    std::swap(*this, tmp);
    return *this;
}

bigint&
bigint::operator<<=(uint32_t i) {
    bigint tmp;
    FC_ASSERT(tmp.n != nullptr);
    FC_ASSERT(n != nullptr);
    BN_lshift(tmp.n, n, i);
    std::swap(*this, tmp);
    return *this;
}

bigint
bigint::operator-(const bigint& a) const {
    bigint tmp;
    BN_sub(tmp.n, n, a.n);
    return tmp;
}
bigint
bigint::exp(const bigint& a) const {
    BN_CTX* ctx = BN_CTX_new();
    bigint  tmp;
    BN_exp(tmp.n, n, a.n, ctx);
    BN_CTX_free(ctx);
    return tmp;
}

bigint&
bigint::operator=(bigint&& a) {
    fc_swap(a.n, n);
    return *this;
}
bigint&
bigint::operator=(const bigint& a) {
    if(&a == this)
        return *this;
    BN_copy(n, a.n);
    return *this;
}
bigint::operator fc::string() const {
    return BN_bn2dec(n);
}

bigint::operator std::vector<char>() const {
    std::vector<char> to(BN_num_bytes(n));
    BN_bn2bin(n, (unsigned char*)to.data());
    return to;
}

/** encodes the big int as base64 string, or a number */
void
to_variant(const bigint& bi, variant& v) {
    std::vector<char> ve = bi;
    v                    = fc::variant(base64_encode((unsigned char*)ve.data(), ve.size()));
}

/** decodes the big int as base64 string, or a number */
void
from_variant(const variant& v, bigint& bi) {
    if(v.is_numeric())
        bi = bigint(static_cast<unsigned long>(v.as_uint64()));
    else {
        std::string b64 = v.as_string();
        std::string bin = base64_decode(b64);
        bi              = bigint(bin.c_str(), bin.size());
    }
}

}  // namespace fc
